CloudFormationのDynamic referencesとAWS SAMのAutoPublishAliasを組み合わせた動作を確認してみた
先日の下記アップデートを活用すれば、Lambda LayersとLambdaのデプロイをスッキリできそうだったので、実際に試してみました。
以前、AWS SAMでAutoPublishAlias
を使った際の動作を試してみたので、本記事でもAutoPublishAlias
を使った際の動作を確認します。
おすすめの方
- AWS SAMでDynamic referencesを使いたい方
- AutoPublishAliasとLambda LayerとLambdaのデプロイについて知りたい方
最初に結論
- Lambda Layersのみを更新すると、Lambda Layersは更新された
- しかし、Lambdaの新バージョン発行とエイリアス更新はされなかった
環境
項目 | バージョン |
---|---|
AWS SAM CLI | 1.21.1 |
3つのCloudFormationスタックを作成する
3つのCloudFormationスタックを作成します。
- Lambda Layersのみ
- API Gateway + Lambda (その1)
- API Gateway + Lambda (その2)
各Lambdaは、同じLambda Layersを参照します。
Lambda LayersとLambdaをデプロイする
sam init
3つのプロジェクトを作成します。
- sam-lambda-layers
- sam-lambda-1
- sam-lambda-2
sam init \ --runtime python3.8 \ --name sam-lambda-layers \ --app-template hello-world \ --package-type Zip
sam init \ --runtime python3.8 \ --name sam-lambda-1 \ --app-template hello-world \ --package-type Zip
sam init \ --runtime python3.8 \ --name sam-lambda-2 \ --app-template hello-world \ --package-type Zip
SAMテンプレート
Lambda Layers
Lambda LayersとSSMパラメータを作成しています。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: sam-lambda-layers Resources: TestDeployLayer: Type: AWS::Serverless::LayerVersion Metadata: BuildMethod: python3.8 Properties: LayerName: test-deploy-layer ContentUri: layer CompatibleRuntimes: - python3.8 TestDeployLayerSsmParameter: Type: AWS::SSM::Parameter Properties: Name: /Fujii/Test/MyLayer Type: String Value: !Ref TestDeployLayer
Lambda1 (Stack 1)
Lambdaを作成しています。Lambda Layersは、Dynamic ReferencesでSSMパラメータストアの最新バージョンを参照しています。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: sam-lambda-1 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: FunctionName: lambda-deploy-sample-1-function CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.8 AutoPublishAlias: dev Timeout: 10 Layers: - '{{resolve:ssm:/Fujii/Test/MyLayer}}' Events: HelloWorld: Type: Api Properties: Path: /hello Method: get HelloWorldFunctionLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${HelloWorldFunction} Outputs: HelloWorldApi: Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
Lambda2 (Stack 2)
Lambda関数名以外は、Stack1と同じです。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: sam-lambda-2 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: FunctionName: lambda-deploy-sample-2-function CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.8 AutoPublishAlias: dev Timeout: 10 Layers: - '{{resolve:ssm:/Fujii/Test/MyLayer}}' Events: HelloWorld: Type: Api Properties: Path: /hello Method: get HelloWorldFunctionLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${HelloWorldFunction} Outputs: HelloWorldApi: Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
ソースコード
Lambda Layers
def hello(): return 'hello'
Lambda1 (Stack 1)
Lambda Layersのコードを呼んでいます。どちらのLambdaが実行されたのかが分かるように番号も明記しています。
import json import my_layer def lambda_handler(event, context): return { "statusCode": 200, "body": json.dumps({ "lambda": "1", "message": my_layer.hello(), }), }
Lambda2 (Stack 2)
Lambda Layerのコードを呼んでいます。
import json import my_layer def lambda_handler(event, context): return { "statusCode": 200, "body": json.dumps({ "lambda": "2", "message": my_layer.hello(), }), }
デプロイ
注意点
AWS SAM CLIは、本記事の作成時点において、Dynamic Referencesに対応していません。
- Unable to resolve SSM parameters in AWS::Serverless::Function Layers · Issue #2743 · aws/aws-sam-cli
そのため、Lambdaコード側でsam build
コマンドを実行すると、下記のエラーで失敗します。
$ sam build Error: {{resolve:ssm:/Fujii/Test/MyLayer}} is an Invalid Layer Arn.
もし、外部ライブラリ等を使う場合は、Lambda Layersに含めると良いですね。
Lambda Layers
Lambda Layersでは、Dynamic Referencesを使っていないため、sam
コマンドが利用できます。
cd sam-lambda-layers sam build --use-container sam deploy \ --stack-name SAM-Lambda-Layers-Stack \ --s3-bucket cm-fujii.genki-deploy \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset
Lambda1 (Stack 1)
Lambdaでは、Dynamic Referencesを使っているため、sam
コマンドではなくaws
コマンドを利用しています。
cd sam-lambda-1 aws cloudformation package \ --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-deploy aws cloudformation deploy \ --template-file packaged.yaml \ --stack-name SAM-Lambda-1-Stack \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset
Lambda2 (Stack 2)
cd sam-lambda-2 aws cloudformation package \ --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-deploy aws cloudformation deploy \ --template-file packaged.yaml \ --stack-name SAM-Lambda-2-Stack \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset
動作確認
APIエンドポイントを取得する
$ aws cloudformation describe-stacks \ --stack-name SAM-Lambda-1-Stack \ --query 'Stacks[].Outputs' [ [ { "OutputKey": "HelloWorldApi", "OutputValue": "https://rx536ihwd7.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/" } ] ]
$ aws cloudformation describe-stacks \ --stack-name SAM-Lambda-2-Stack \ --query 'Stacks[].Outputs' [ [ { "OutputKey": "HelloWorldApi", "OutputValue": "https://66yrzneyti.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/" } ] ]
APIから情報取得する
バッチリですね。
$ curl https://rx536ihwd7.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ {"lambda": "1", "message": "hello"}
$ curl https://66yrzneyti.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ {"lambda": "2", "message": "hello"}
SSMパラメータストアの内容を確認する
test-deploy-layer:1
が格納されています。
$ aws ssm get-parameters \ --names /Fujii/Test/MyLayer \ --query Parameters[*].[Name,Type,Value,Version] [ [ "/Fujii/Test/MyLayer", "String", "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1", 1 ] ]
API GatewayとLambdaの関係を確認する
Lambda1 (Stack 1)
作成したAPIのリソース情報を取得します。rx536ihwd7
部分は、API GatewayのIDです(URLの先頭部分)。そして、レスポンスのGET /helloのリソースID(rm8kns
)をメモしておきます。
$ aws apigateway get-resources \ --rest-api-id rx536ihwd7 \ --query items [ { "id": "rm8kns", "parentId": "wtvajqmwx9", "pathPart": "hello", "path": "/hello", "resourceMethods": { "GET": {} } }, { "id": "wtvajqmwx9", "path": "/" } ]
API GatewayのMethod情報を取得し、どのLambdaが呼ばれているのかを確認します。alias:dev
付きでlambda-deploy-sample-1-function:dev
が呼ばれています。
$ aws apigateway get-method \ --rest-api-id rx536ihwd7 \ --resource-id rm8kns \ --http-method GET \ --query methodIntegration.[uri] [ "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-deploy-sample-1-function:dev/invocations" ]
Lambda2 (Stack 2)
もうひとつのAPIも確認します。
$ aws apigateway get-resources \ --rest-api-id 66yrzneyti \ --query items [ { "id": "gq0bdw", "parentId": "quwnfgtni0", "pathPart": "hello", "path": "/hello", "resourceMethods": { "GET": {} } }, { "id": "quwnfgtni0", "path": "/" } ]
alias:dev
付きでlambda-deploy-sample-2-function:dev
が呼ばれています。
$ aws apigateway get-method \ --rest-api-id 66yrzneyti \ --resource-id gq0bdw \ --http-method GET \ --query methodIntegration.[uri] [ "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-deploy-sample-2-function:dev/invocations" ]
Lambdaのエイリアスとバージョンを確認する
Lambda1 (Stack 1)
$LATEST
と1
の2つのバージョンがあります。それぞれtest-deploy-layer:1
を参照しています。
$ aws lambda list-functions \ --function-version ALL \ --query Functions[?FunctionName==\'lambda-deploy-sample-1-function\'].[Version,Layers[*].Arn] [ [ "$LATEST", [ "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1" ] ], [ "1", [ "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1" ] ] ]
alias:dev
は、Lambda Version:1
であり、test-deploy-layer:1
を参照しています。
$ aws lambda get-function \ --function-name lambda-deploy-sample-1-function:dev \ --query Configuration.[Version,Layers] [ "1", [ { "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1", "CodeSize": 401 } ] ]
Lambda2 (Stack 2)
$LATEST
と1
の2つのバージョンがあります。それぞれtest-deploy-layer:1
を参照しています。
$ aws lambda list-functions \ --function-version ALL \ --query Functions[?FunctionName==\'lambda-deploy-sample-2-function\'].[Version,Layers[*].Arn] [ [ "$LATEST", [ "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1" ] ], [ "1", [ "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1" ] ] ]
alias:dev
は、Lambda Version:1
であり、test-deploy-layer:1
を参照しています。
$ aws lambda get-function \ --function-name lambda-deploy-sample-2-function:dev \ --query Configuration.[Version,Layers] [ "1", [ { "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1", "CodeSize": 401 } ] ]
Lambda Layersを確認する
Lambda Layersは、version:1
だけ存在しています。
$ aws lambda list-layer-versions \ --layer-name test-deploy-layer \ --query LayerVersions[*].[Version] [ [ 1 ] ]
ここまでの関係を絵で整理する
Lambda Layersのコードだけ変更する
Lambda Layersのコード
hello
からhello world!!
に変更しました。
def hello(): return 'hello world!!'
デプロイ
デプロイコマンドはすべて同じです。
Lambda Layers
cd sam-lambda-layers sam build --use-container sam deploy \ --stack-name SAM-Lambda-Layers-Stack \ --s3-bucket cm-fujii.genki-deploy \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset
Lambda1
cd sam-lambda-1 aws cloudformation package \ --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-deploy aws cloudformation deploy \ --template-file packaged.yaml \ --stack-name SAM-Lambda-1-Stack \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset
Lambda2
cd sam-lambda-2 aws cloudformation package \ --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-deploy aws cloudformation deploy \ --template-file packaged.yaml \ --stack-name SAM-Lambda-2-Stack \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset
動作確認
APIから情報取得する
Lambda Layersのデプロイは、反映されませんでした。
$ curl https://rx536ihwd7.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ {"lambda": "1", "message": "hello"}
$ curl https://66yrzneyti.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ {"lambda": "2", "message": "hello"}
SSMパラメータストアの内容を確認する
test-deploy-layer:2
が格納されています。
$ aws ssm get-parameters \ --names /Fujii/Test/MyLayer \ --query Parameters[*].[Name,Type,Value,Version] [ [ "/Fujii/Test/MyLayer", "String", "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:2", 2 ] ]
API GatewayとLambdaの関係を確認する
Lambda1 (Stack 1)
API GatewayのMethod情報を取得し、どのLambdaが呼ばれているのかを確認します。alias:dev
付きでlambda-deploy-sample-1-function:dev
が呼ばれています。こちらは今まで通りですね。
$ aws apigateway get-method \ --rest-api-id rx536ihwd7 \ --resource-id rm8kns \ --http-method GET \ --query methodIntegration.[uri] [ "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-deploy-sample-1-function:dev/invocations" ]
Lambda2 (Stack 2)
もうひとつのAPIも同様です。
alias:dev
付きでlambda-deploy-sample-2-function:dev
が呼ばれています。
$ aws apigateway get-method \ --rest-api-id 66yrzneyti \ --resource-id gq0bdw \ --http-method GET \ --query methodIntegration.[uri] [ "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-deploy-sample-2-function:dev/invocations" ]
Lambdaのエイリアスとバージョンを確認する
Lambda1 (Stack 1)
$LATEST
と1
の2つのバージョンがあります。$LATEST
はtest-deploy-layer:2
の参照に変わっていますが、1
はtest-deploy-layer:1
から変化していません。
$ aws lambda list-functions \ --function-version ALL \ --query Functions[?FunctionName==\'lambda-deploy-sample-1-function\'].[Version,Layers[*].Arn] [ [ "$LATEST", [ "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:2" ] ], [ "1", [ "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1" ] ] ]
alias:dev
は、Lambda Version:1
であり、test-deploy-layer:1
を参照しています。
$ aws lambda get-function \ --function-name lambda-deploy-sample-1-function:dev \ --query Configuration.[Version,Layers] [ "1", [ { "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1", "CodeSize": 401 } ] ]
Lambda2 (Stack 2)
$LATEST
と1
の2つのバージョンがあります。$LATEST
はtest-deploy-layer:2
の参照に変わっていますが、1
はtest-deploy-layer:1
から変化していません。
$ aws lambda list-functions \ --function-version ALL \ --query Functions[?FunctionName==\'lambda-deploy-sample-2-function\'].[Version,Layers[*].Arn] [ [ "$LATEST", [ "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:2" ] ], [ "1", [ "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1" ] ] ]
alias:dev
は、Lambda Version:1
であり、test-deploy-layer:1
を参照しています。
$ aws lambda get-function \ --function-name lambda-deploy-sample-2-function:dev \ --query Configuration.[Version,Layers] [ "1", [ { "Arn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:test-deploy-layer:1", "CodeSize": 401 } ] ]
Lambda Layersを確認する
Lambda Layersは、version:1
とversion:2
が存在しています。増えました。
$ aws lambda list-layer-versions \ --layer-name test-deploy-layer \ --query LayerVersions[*].[Version] [ [ 2 ], [ 1 ] ]
ここまでの関係を絵で整理する
さいごに
AWS SAM CLIでDynamic referencesをサポートしていない部分でハマりました。どなたかの参考になれば幸いです。
参考
- 動的な参照を使用してテンプレート値を指定する - AWS CloudFormation
- 【アップデート】スタック間のパラメータ渡しが便利に!!CloudFormationのDynamic referencesでSSMパラメータの最新バージョンが参照可能になりました | DevelopersIO
- Lambda Layerだけを更新したとき、Lambdaが参照するLayerのバージョンが追従しないのでAutoPublishAliasを止めてみた話 | DevelopersIO
- Unable to resolve SSM parameters in AWS::Serverless::Function Layers · Issue #2743 · aws/aws-sam-cli